{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "190c69e4-1a17-46bf-a6ea-79760b4ef4ca",
   "metadata": {},
   "source": [
    "# 标记和提取\n",
    "\n",
    " - [一、设置OpenAI API Key](#一、设置OpenAI-API-Key)\n",
    " - [二、Tagging](#二、Tagging)\n",
    "     - [2.1 创建Tagging函数](#2.1-创建Tagging函数)\n",
    "     - [2.2 通过LangChain实现Tagging](#2.2-通过LangChain实现Tagging)\n",
    "     - [2.3 结构化解析Tagging结果](#2.3-结构化解析Tagging结果)\n",
    " - [三、 Extraction](#三、Extraction)\n",
    "     - [3.1 创建Extraction函数](#3.1-创建Extraction函数)\n",
    "     - [3.2 通过LangChain实现创建Extraction函数](#3.2-通过LangChain实现创建Extraction函数)\n",
    "     - [3.3 结构化解析Extraction结果](#3.3-结构化解析Extraction结果)\n",
    " - [四、应用案例](#四、应用案例)\n",
    "     - [4.1 加载数据](#4.1-加载数据)\n",
    "     - [4.2 提取文章概览](#4.2-提取文章概览)\n",
    "     - [4.3 提取文章信息](#4.3-提取文章信息)\n",
    "     - [4.4 分块文本提取](#4.4-分块文本提取)\n",
    "         "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff54f3b7-ff99-453c-a5cd-e6fb47569a50",
   "metadata": {},
   "source": [
    "# 一、设置OpenAI-API-Key\n",
    "\n",
    "详细内容见`设置OpenAI_API_KEY.ipynb`文件"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf894094-198d-429d-96da-f7092cd7c44e",
   "metadata": {},
   "source": [
    "# 二、Tagging"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22e2fb4b-3678-48e2-bd81-edb485dfee05",
   "metadata": {},
   "source": [
    "`Tagging` 是什么：\n",
    "- LLM 给出一个函数描述，从输入文本中选择参数生成一个结构化的输出，形成函数调用\n",
    "- 更一般地说，LLM 可以评估输入文本并生成**结构化输出**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29d4ebb3",
   "metadata": {},
   "source": [
    "## 2.1 创建Tagging函数"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "081ba8f1",
   "metadata": {},
   "source": [
    "我们定义了一个`Tagging`，它继承自 Pydantic 的 BaseModel 类，因此`Tagging`类也具备了严格的数据类型校验功能。`Tagging`类包含了2给成员变量：`sentiment`和`language`：\n",
    "- `sentiment`：用来判断用户信息的情感包括 pos(正面)，neg(负面)，neutral(中立)。\n",
    "- `language`：用来判断用户使用的是哪国的语言，并且要符合 ISO 639-1 编码规范。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a3d17cf",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入模块\n",
    "from typing import List  \n",
    "from pydantic import BaseModel, Field  \n",
    "from langchain.utils.openai_functions import convert_pydantic_to_openai_function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "108f2535",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建 Tagging 类\n",
    "# 该类表是基于输入的文本来标记文本情感的 `pos`（正面）、`neg`（负面）或`neutral`（中立）\n",
    "class Tagging(BaseModel):\n",
    "    \"\"\"用特定信息标记这段文本。\"\"\"\n",
    "    # 文本的情绪标签，可选值为`pos`（正面）、`neg`（负面）或`neutral`（中立）\n",
    "    sentiment: str = Field(description=\"文本的情绪，请从“正面”、“负面”或“中立”中选择\")\n",
    "    # 文本的语言标签，应为ISO 639-1标准代码\n",
    "    language: str = Field(description=\"文本语言(应采用ISO 639-1代码)\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "74150c5a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'name': 'Tagging',\n",
       " 'description': '用特定信息标记这段文本。',\n",
       " 'parameters': {'title': 'Tagging',\n",
       "  'description': '用特定信息标记这段文本。',\n",
       "  'type': 'object',\n",
       "  'properties': {'sentiment': {'title': 'Sentiment',\n",
       "    'description': '文本的情绪，请从“正面”、“负面”或“中立”中选择',\n",
       "    'type': 'string'},\n",
       "   'language': {'title': 'Language',\n",
       "    'description': '文本语言(应采用ISO 639-1代码)',\n",
       "    'type': 'string'}},\n",
       "  'required': ['sentiment', 'language']}}"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 将Tagging数据模型转换为OpenAI函数\n",
    "convert_pydantic_to_openai_function(Tagging)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4440590",
   "metadata": {},
   "source": [
    "## 2.2 通过LangChain实现Tagging"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d245ef62",
   "metadata": {},
   "source": [
    "接下来我们要将`Tagging`类转换成一个openai能识别的函数描述对象"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ab35dd5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入模块\n",
    "from langchain.prompts import ChatPromptTemplate \n",
    "from langchain.chat_models import ChatOpenAI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b6795dfd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建一个温度为0的ChatOpenAI模型实例\n",
    "model = ChatOpenAI(temperature=0)  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c78f4938",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 应用 Tagging \n",
    "tagging_functions = [convert_pydantic_to_openai_function(Tagging)]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "988d8550",
   "metadata": {},
   "source": [
    "有了函数描述变量，我们使用`LCEL`语法来创建一个 chain。在这之前我们需要创建 prompt，model，并绑定函数描述变量最后创建 chain。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a8ba9c2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用ChatPromptTemplate的from_messages方法创建聊天提示模板\n",
    "prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"仔细思考，然后按指示标记文本\"),\n",
    "    (\"user\", \"{input}\")\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5b3061eb",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 将模型与函数绑定，并指定函数调用的名称\n",
    "model_with_functions = model.bind(\n",
    "    functions=tagging_functions,\n",
    "    function_call={\"name\": \"Tagging\"}\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "52bc969f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建一个标签链，结合提示模板和模型\n",
    "tagging_chain = prompt | model_with_functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "df8f070b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='', additional_kwargs={'function_call': {'name': 'Tagging', 'arguments': '{\\n  \"sentiment\": \"正面\",\\n  \"language\": \"zh\"\\n}'}}, example=False)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 调用标签链并传入输入文本\n",
    "tagging_chain.invoke({\"input\": \"我爱langchain\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "67d3a755",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='', additional_kwargs={'function_call': {'name': 'Tagging', 'arguments': '{\\n  \"sentiment\": \"中立\",\\n  \"language\": \"zh\"\\n}'}}, example=False)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 再次调用标签链并传入另一个输入文本\n",
    "tagging_chain.invoke({\"input\": \"我想要问的不是这些问题\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4dcc4fd",
   "metadata": {},
   "source": [
    "## 2.3 结构化解析Tagging结果"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "da30edaa",
   "metadata": {},
   "source": [
    "以上输出 LLM 给出的 AIMessage 格式的结果，我们可以利用`LCEL`语法，在创建 chain 的时候附加一个 json 的输出解析器就可以解决这个问题。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cfa1f565",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 从langchain.output_parsers.openai_functions模块导入JsonOutputFunctionsParser\n",
    "from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7da0d639",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建一个新的标签链，结合提示模板、模型和JsonOutputFunctionsParser解析器\n",
    "tagging_chain = prompt | model_with_functions | JsonOutputFunctionsParser()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bea84888",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'sentiment': '正面', 'language': 'zh'}"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 调用标签链并传入输入文本\n",
    "tagging_chain.invoke({\"input\": \"我爱langchain\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14a85c10",
   "metadata": {},
   "source": [
    "# 三、Extraction"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ba79dbfe",
   "metadata": {},
   "source": [
    "Extraction 是什么：\n",
    "- 提取（Extraction）类似于标记（Tagging），但用于提取多条信息。\n",
    "- 当给定一个输入Json模式时，LLM已经进行了微调，以查找并填充该模式的参数。\n",
    "- 该功能并不局限于function模式，可以用于一般用途的提取。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c8b77f8",
   "metadata": {},
   "source": [
    "## 3.1 创建Extraction函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5c9ebd32",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入模块\n",
    "from typing import Optional  \n",
    "from pydantic import BaseModel, Field  "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81783873",
   "metadata": {},
   "source": [
    "定义了`Person`和`Information`两个类：\n",
    "- `person`类包含了2个成员，name和age，其中age是可选的。\n",
    "- `Information`类包含了一个people成员，它一个person的集合(List)。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6a9838da",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建Person类\n",
    "class Person(BaseModel):\n",
    "    \"\"\"个人信息\"\"\"\n",
    "    name: str = Field(description=\"人的名字\")  # 人的名字\n",
    "    age: Optional[int] = Field(description=\"人的年龄\")  # 人的年龄，可选字段"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ba57cb12",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建Information类别\n",
    "class Information(BaseModel):\n",
    "    \"\"\"要提取的信息\"\"\"\n",
    "    people: List[Person] = Field(description=\"关于人的信息列表\")  # 关于人的信息列表"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9a5dd9a4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'name': 'Information',\n",
       " 'description': '要提取的信息',\n",
       " 'parameters': {'title': 'Information',\n",
       "  'description': '要提取的信息',\n",
       "  'type': 'object',\n",
       "  'properties': {'people': {'title': 'People',\n",
       "    'description': '关于人的信息列表',\n",
       "    'type': 'array',\n",
       "    'items': {'title': 'Person',\n",
       "     'description': '个人信息',\n",
       "     'type': 'object',\n",
       "     'properties': {'name': {'title': 'Name',\n",
       "       'description': '人的名字',\n",
       "       'type': 'string'},\n",
       "      'age': {'title': 'Age', 'description': '人的年龄', 'type': 'integer'}},\n",
       "     'required': ['name']}}},\n",
       "  'required': ['people']}}"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 将Information数据模型转换为OpenAI函数\n",
    "convert_pydantic_to_openai_function(Information)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f883a61b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建提取功能列表，并将提取功能绑定到模型上\n",
    "extraction_functions = [convert_pydantic_to_openai_function(Information)]  \n",
    "extraction_model = model.bind(functions=extraction_functions, function_call={\"name\": \"Information\"})  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "68313aad",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='', additional_kwargs={'function_call': {'name': 'Information', 'arguments': '{\\n  \"people\": [\\n    {\\n      \"name\": \"乔\",\\n      \"age\": 30\\n    },\\n    {\\n      \"name\": \"玛莎\",\\n      \"age\": 0\\n    }\\n  ]\\n}'}}, example=False)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 调用提取模型，传入文本信息\n",
    "extraction_model.invoke(\"乔30岁，他妈妈叫玛莎\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "92d007db",
   "metadata": {},
   "source": [
    "## 3.2 通过LangChain实现创建Extraction函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "252aa292",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用ChatPromptTemplate创建提示模板\n",
    "prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"提取相关信息，如果没有明确提供不要猜测。可以提取部分信息\"), \n",
    "    (\"human\", \"{input}\")  \n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e6e99c0a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建提取链，结合提示模板和提取模型\n",
    "extraction_chain = prompt | extraction_model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fbf3fa31",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='', additional_kwargs={'function_call': {'name': 'Information', 'arguments': '{\\n  \"people\": [\\n    {\\n      \"name\": \"乔\",\\n      \"age\": 30\\n    },\\n    {\\n      \"name\": \"玛莎\"\\n    }\\n  ]\\n}'}}, example=False)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 调用提取链并传入输入文本\n",
    "extraction_chain.invoke({\"input\": \"乔30岁，他妈妈叫玛莎\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "316f399c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建新的提取链，加入JsonOutputFunctionsParser来解析输出\n",
    "extraction_chain = prompt | extraction_model | JsonOutputFunctionsParser()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cf1b986c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'people': [{'name': '乔', 'age': 30}, {'name': '玛莎'}]}"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 再次调用提取链\n",
    "extraction_chain.invoke({\"input\": \"乔30岁，他妈妈叫玛莎\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ce8b57f8",
   "metadata": {},
   "source": [
    "## 3.3 结构化解析Extraction结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8d3e6de4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入模块\n",
    "from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d8eb37cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建提取链，指定关键字\"name\"来解析输出\n",
    "extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name=\"people\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a3fd2d8c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'name': '乔', 'age': 30}, {'name': '玛莎'}]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 调用提取链并传入输入文本\n",
    "extraction_chain.invoke({\"input\": \"乔30岁，他妈妈叫玛莎\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20c15c6b",
   "metadata": {},
   "source": [
    "# 四、应用案例"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2dbc0bd6",
   "metadata": {},
   "source": [
    "我们可以对更大的文本主体应用标记。例如，加载博客文章并从文本的子集中提取标记信息。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ebdb6951",
   "metadata": {},
   "source": [
    "## 4.1 加载数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d647ae05",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用WebBaseLoader加载文档\n",
    "from langchain.document_loaders import WebBaseLoader  \n",
    "loader = WebBaseLoader(\"https://lilianweng.github.io/posts/2023-06-23-agent/\") \n",
    "documents = loader.load() "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5c26230f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 获取第一个文档\n",
    "doc = documents[0]  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f977416a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 获取页面内容的前10000个字符\n",
    "page_content = doc.page_content[:10000]  "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76a3c7f4",
   "metadata": {},
   "source": [
    "## 4.2 提取文章概览"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c7008c06",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 从pydantic导入BaseModel和Field用于创建数据模型\n",
    "from pydantic import BaseModel, Field  "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1010fc64",
   "metadata": {},
   "source": [
    "定义一个Pydantic类`Overview`\n",
    "- `summary`：表示对文章内容的总结\n",
    "- `language`：表示文章所使用的语言\n",
    "- `keyword`：表示文章中的关键词"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "226ed0ee",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建Overview类别\n",
    "class Overview(BaseModel):\n",
    "    \"\"\"一段文本的概述\"\"\"\n",
    "    summary: str = Field(description=\"提供内容的简明总结。\")  # 内容摘要\n",
    "    language: str = Field(description=\"提供编写内容所用的语言。\")  # 内容语言\n",
    "    keywords: str = Field(description=\"提供与内容相关的关键字。\")  # 关键词"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9173caa",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 将Overview数据模型转换为OpenAI函数\n",
    "overview_tagging_function = [\n",
    "    convert_pydantic_to_openai_function(Overview)\n",
    "]\n",
    "tagging_model = model.bind(\n",
    "    functions=overview_tagging_function,\n",
    "    function_call={\"name\":\"Overview\"}  # 绑定函数调用\n",
    ")\n",
    "tagging_chain = prompt | tagging_model | JsonOutputFunctionsParser()  # 创建标注链并加入解析器"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ab4c06b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'summary': 'LLM Powered Autonomous Agents is a concept of building agents with LLM (large language model) as its core controller. It involves several key components such as planning, memory, and tool use. The agent breaks down tasks into smaller subgoals, utilizes short-term and long-term memory, and learns to call external APIs for additional information. Self-reflection is also an important aspect for agents to improve iteratively. There are various techniques and frameworks, such as Chain of Thought, ReAct, Reflexion, and Chain of Hindsight, that enable agents to plan, reflect, and improve their performance.',\n",
       " 'language': 'English',\n",
       " 'keywords': 'LLM, autonomous agents, planning, memory, tool use, self-reflection, Chain of Thought, ReAct, Reflexion, Chain of Hindsight'}"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 调用标注链\n",
    "tagging_chain.invoke({\"input\": page_content})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d7d884b6",
   "metadata": {},
   "source": [
    "## 4.3 提取文章信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e9305294",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建Paper类，用于标题和作者\n",
    "class Paper(BaseModel):\n",
    "    \"\"\"提到的论文信息。\"\"\"\n",
    "    title: str  # 论文标题\n",
    "    author: Optional[str]  # 作者，可选字段\n",
    "\n",
    "# 创建Info，用户提取论文论文信息列表\n",
    "class Info(BaseModel):\n",
    "    \"\"\"要提取的信息\"\"\"\n",
    "    papers: List[Paper] "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "07444034",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 将Info数据模型转换为OpenAI函数\n",
    "paper_extraction_function = [\n",
    "    convert_pydantic_to_openai_function(Info)\n",
    "]\n",
    "extraction_model = model.bind(\n",
    "    functions=paper_extraction_function, \n",
    "    function_call={\"name\":\"Info\"}  # 绑定函数调用\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "93b2adae",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建提取链并加入解析器\n",
    "extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name=\"papers\") "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e3151bcd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'title': 'LLM Powered Autonomous Agents', 'author': 'Lilian Weng'}]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 调用提取链，发现提取了论文本身的名称。因此接下里可以结合prompt改进\n",
    "extraction_chain.invoke({\"input\": page_content})  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4ddd8d04",
   "metadata": {},
   "outputs": [],
   "source": [
    "template = \"\"\"\n",
    "A article will be passed to you. Extract from it all papers that are mentioned by this article. \n",
    "Do not extract the name of the article itself. If no papers are mentioned that's fine - you don't need to extract any! Just return an empty list.\n",
    "Do not make up or guess ANY extra information. Only extract what exactly is in the text.\n",
    "\"\"\"\n",
    "\n",
    "template_chinese = \"\"\"\n",
    "一篇文章将转交给你。把这篇文章中提到的所有论文都摘录出来。\n",
    "不要提取文章本身的名称。如果没有提到论文，那很好——你不需要提取任何论文!只返回一个空列表。\n",
    "不要编造或猜测任何额外的信息。只提取文本中的内容。\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "04af6d1e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用定制化提示模板创建聊天提示\n",
    "prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", template_chinese),\n",
    "    (\"human\", \"{input}\")\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "da5df8c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 重新创建提取链\n",
    "extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name=\"papers\")  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "75c7f9d8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'title': 'Chain of thought (CoT; Wei et al. 2022)', 'author': ''},\n",
       " {'title': 'Tree of Thoughts (Yao et al. 2023)', 'author': ''},\n",
       " {'title': 'LLM+P (Liu et al. 2023)', 'author': ''},\n",
       " {'title': 'ReAct (Yao et al. 2023)', 'author': ''},\n",
       " {'title': 'Reflexion (Shinn & Labash 2023)', 'author': ''},\n",
       " {'title': 'Chain of Hindsight (CoH; Liu et al. 2023)', 'author': ''},\n",
       " {'title': 'Algorithm Distillation (AD; Laskin et al. 2023)', 'author': ''}]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 再次调用提取链\n",
    "extraction_chain.invoke({\"input\": page_content})  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a304665d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 使用不相关输入调用提取链，不会返回有效信息\n",
    "extraction_chain.invoke({\"input\": \"hi\"})  "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53459cd8",
   "metadata": {},
   "source": [
    "## 4.4 分块文本提取"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33f32ae3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入模块\n",
    "from langchain.text_splitter import RecursiveCharacterTextSplitter \n",
    "\n",
    "# 实例化文本分割器\n",
    "text_splitter = RecursiveCharacterTextSplitter(chunk_overlap=0)  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f0f8598",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "14"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 分割文档内容，text_splitter可以将长文本切分成多个短文本\n",
    "splits = text_splitter.split_text(doc.page_content)  \n",
    "\n",
    "# 获取分割后的段落数量\n",
    "len(splits)  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4c57c44c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义函数用于扁平化列表\n",
    "def flatten(matrix):\n",
    "    flat_list = []\n",
    "    for row in matrix:\n",
    "        flat_list += row\n",
    "    return flat_list  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d51306c1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1, 2, 3, 4]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 示例调用扁平化函数\n",
    "flatten([[1, 2], [3, 4]])  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "681b968b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "lemented by several key components:\n",
      "\n",
      "Planning\n",
      "\n",
      "Subgoal and decomposition: The agent breaks down large tasks into smaller, manageable subgoals, enabling efficient handling of complex tasks.\n",
      "Reflection and refinement: The agent can do self-criticism and self-reflection over past actions, learn from mistakes and refine them for future steps, thereby improving the quality of final results.\n",
      "\n",
      "\n",
      "Memory\n",
      "\n",
      "Short-term memory: I would consider all the in-context learning (See Prompt Engineering) as utilizing short-term memory of the model to learn.\n",
      "Long-term memory: This provides the agent with the capability to retain and recall (infinite) information over extended periods, often by leveraging an external vector store and fast retrieval.\n",
      "\n",
      "\n",
      "Tool use\n",
      "\n",
      "The agent learns to call external APIs for extra information that is missing from the model weights (often hard to change after pre-training), including current information, code execution capability, access to proprietary information sources and more.\n"
     ]
    }
   ],
   "source": [
    "# 打印第一个分割的文本块最后一千个字符\n",
    "print(splits[0][-1000:])  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2c780252",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入模块\n",
    "from langchain.schema.runnable import RunnableLambda  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "158a7bb2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建Lambda函数用于预处理文本\n",
    "prep = RunnableLambda(\n",
    "    lambda x: [{\"input\": doc} for doc in text_splitter.split_text(x)]  \n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a9706f5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[{'input': 'hi'}]\n",
      "1\n",
      "14\n"
     ]
    }
   ],
   "source": [
    "# 测试prep\n",
    "print(prep.invoke(\"hi\"))\n",
    "print(len(prep.invoke(\"hi\")))\n",
    "\n",
    "# 将长文本放入，会切分成多个短文本\n",
    "print(len(prep.invoke(doc.page_content)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0b4bac5d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建链式调用，包括预处理、映射提取\n",
    "# 多个短文本分别使用extraction_chain进行提取，将结果的list通过flatten函数扁平化到一起\n",
    "chain = prep | extraction_chain.map() | flatten  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b2e6e4f1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'title': 'AutoGPT', 'author': ''},\n",
       " {'title': 'GPT-Engineer', 'author': ''},\n",
       " {'title': 'BabyAGI', 'author': ''},\n",
       " {'title': 'Chain of thought (CoT; Wei et al. 2022)', 'author': ''},\n",
       " {'title': 'Tree of Thoughts (Yao et al. 2023)', 'author': ''},\n",
       " {'title': 'LLM+P (Liu et al. 2023)', 'author': ''},\n",
       " {'title': 'ReAct (Yao et al. 2023)', 'author': ''},\n",
       " {'title': 'Reflexion (Shinn & Labash 2023)', 'author': ''},\n",
       " {'title': 'Reflexion: A Framework for Self-Reflection in Reinforcement Learning',\n",
       "  'author': 'Shinn & Labash'},\n",
       " {'title': 'Chain of Hindsight: Improving Reinforcement Learning with Sequential Feedback',\n",
       "  'author': 'Liu et al.'},\n",
       " {'title': 'Algorithm Distillation: Learning Process of Reinforcement Learning',\n",
       "  'author': 'Laskin et al.'},\n",
       " {'title': 'Algorithm Distillation', 'author': 'Laskin et al. 2023'},\n",
       " {'title': 'ED (expert distillation)', 'author': ''},\n",
       " {'title': 'RL^2', 'author': 'Duan et al. 2017'},\n",
       " {'title': 'Maximum Inner Product Search (MIPS)', 'author': ''},\n",
       " {'title': 'LSH (Locality-Sensitive Hashing)', 'author': ''},\n",
       " {'title': 'ANNOY (Approximate Nearest Neighbors Oh Yeah)', 'author': ''},\n",
       " {'title': 'HNSW (Hierarchical Navigable Small World)', 'author': ''},\n",
       " {'title': 'FAISS (Facebook AI Similarity Search)', 'author': ''},\n",
       " {'title': 'ScaNN (Scalable Nearest Neighbors)', 'author': ''},\n",
       " {'title': 'MRKL: Modular Reasoning, Knowledge and Language',\n",
       "  'author': 'Karpas et al. 2022'},\n",
       " {'title': 'TALM: Tool Augmented Language Models',\n",
       "  'author': 'Parisi et al. 2022'},\n",
       " {'title': 'Toolformer', 'author': 'Schick et al. 2023'},\n",
       " {'title': 'HuggingGPT', 'author': 'Shen et al. 2023'},\n",
       " {'title': 'API-Bank: A Benchmark for Evaluating Tool-Augmented Language Models',\n",
       "  'author': 'Li et al. 2023'},\n",
       " {'title': 'ChemCrow: A Domain-Specific Example of Tool-Augmented Language Models',\n",
       "  'author': 'Bran et al. 2023'},\n",
       " {'title': 'LLM-based evaluation of GPT-4 and ChemCrow', 'author': 'Unknown'},\n",
       " {'title': 'LLM-empowered agents for scientific discovery',\n",
       "  'author': 'Boiko et al. (2023)'},\n",
       " {'title': 'Generative Agents Simulation', 'author': 'Park, et al. (2023)'},\n",
       " {'title': 'Park et al. 2023', 'author': ''},\n",
       " {'title': 'Super Mario: A Classic Platform Game', 'author': 'John Smith'},\n",
       " {'title': 'MVC Architecture in Python', 'author': 'Jane Doe'},\n",
       " {'title': 'Keyboard Control in Python Games', 'author': 'David Johnson'},\n",
       " {'title': 'A Study on Machine Learning Algorithms', 'author': 'John Smith'},\n",
       " {'title': 'Deep Learning Techniques for Image Recognition',\n",
       "  'author': 'Jane Doe'},\n",
       " {'title': 'Natural Language Processing: A Comprehensive Review',\n",
       "  'author': 'David Johnson'},\n",
       " {'title': 'Chain of thought prompting elicits reasoning in large language models.',\n",
       "  'author': 'Wei et al.'},\n",
       " {'title': 'Tree of Thoughts: Deliberate Problem Solving with Large Language Models.',\n",
       "  'author': 'Yao et al.'},\n",
       " {'title': 'Chain of Hindsight Aligns Language Models with Feedback',\n",
       "  'author': 'Liu et al.'},\n",
       " {'title': 'LLM+P: Empowering Large Language Models with Optimal Planning Proficiency',\n",
       "  'author': 'Liu et al.'},\n",
       " {'title': 'ReAct: Synergizing reasoning and acting in language models.',\n",
       "  'author': 'Yao et al.'},\n",
       " {'title': 'Reflexion: an autonomous agent with dynamic memory and self-reflection',\n",
       "  'author': 'Shinn & Labash'},\n",
       " {'title': 'In-context Reinforcement Learning with Algorithm Distillation',\n",
       "  'author': 'Laskin et al.'},\n",
       " {'title': 'MRKL Systems A modular, neuro-symbolic architecture that combines large language models, external knowledge sources and discrete reasoning.',\n",
       "  'author': 'Karpas et al.'},\n",
       " {'title': 'API-Bank: A Benchmark for Tool-Augmented LLMs',\n",
       "  'author': 'Li et al.'},\n",
       " {'title': 'HuggingGPT: Solving AI Tasks with ChatGPT and its Friends in HuggingFace',\n",
       "  'author': 'Shen et al.'},\n",
       " {'title': 'ChemCrow: Augmenting large-language models with chemistry tools.',\n",
       "  'author': 'Bran et al.'},\n",
       " {'title': 'Emergent autonomous scientific research capabilities of large language models.',\n",
       "  'author': 'Boiko et al.'},\n",
       " {'title': 'Generative Agents: Interactive Simulacra of Human Behavior.',\n",
       "  'author': 'Joon Sung Park, et al.'}]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "chain.invoke(doc.page_content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1027e4c9",
   "metadata": {},
   "source": [
    "# 五、英文版模版"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d92ad4e",
   "metadata": {},
   "source": [
    "**2.1 创建Tagging函数**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "72a19e0d",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Tagging(BaseModel):\n",
    "    \"\"\"Tag the piece of text with particular info.\"\"\"\n",
    "    sentiment: str = Field(description=\"sentiment of text, should be `pos`, `neg`, or `neutral`\")\n",
    "    language: str = Field(description=\"language of text (should be ISO 639-1 code)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ab576c2",
   "metadata": {},
   "source": [
    "**2.2 通过LangChain实现Tagging**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cf7353b4",
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"Think carefully, and then tag the text as instructed\"),\n",
    "    (\"user\", \"{input}\")\n",
    "])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "57ca92a1",
   "metadata": {},
   "source": [
    "**3.1 创建Extraction函数**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ef95c6aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person(BaseModel):\n",
    "    \"\"\"Information about a person.\"\"\"\n",
    "    name: str = Field(description=\"person's name\")  \n",
    "    age: Optional[int] = Field(description=\"person's age\")  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0b5703cf",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Information(BaseModel):\n",
    "    \"\"\"Information to extract.\"\"\"\n",
    "    people: List[Person] = Field(description=\"List of info about people\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3bfc2f8c",
   "metadata": {},
   "source": [
    "**3.2 通过LangChain实现创建Extraction函数**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "203804b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"Extract the relevant information, if not explicitly provided do not guess. Extract partial info\"), \n",
    "    (\"human\", \"{input}\")  \n",
    "])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c612cbbc",
   "metadata": {},
   "source": [
    "**4.2 提取文章概览**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8557237d",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Overview(BaseModel):\n",
    "    \"\"\"Overview of a section of text.\"\"\"\n",
    "    summary: str = Field(description=\"Provide a concise summary of the content.\") \n",
    "    language: str = Field(description=\"Provide the language that the content is written in.\") \n",
    "    keywords: str = Field(description=\"Provide keywords related to the content.\") "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30203abe",
   "metadata": {},
   "source": [
    "**4.3 提取文章信息**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7579872a",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Paper(BaseModel):\n",
    "    \"\"\"Information about papers mentioned.\"\"\"\n",
    "    title: str  \n",
    "    author: Optional[str]  \n",
    "\n",
    "class Info(BaseModel):\n",
    "    \"\"\"Information to extract\"\"\"\n",
    "    papers: List[Paper] "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "498e7dcd",
   "metadata": {},
   "source": [
    "prompt 使用 `template`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8d5899a0",
   "metadata": {},
   "outputs": [],
   "source": [
    "template = \"\"\"\n",
    "A article will be passed to you. Extract from it all papers that are mentioned by this article. \n",
    "Do not extract the name of the article itself. If no papers are mentioned that's fine - you don't need to extract any! Just return an empty list.\n",
    "Do not make up or guess ANY extra information. Only extract what exactly is in the text.\n",
    "\"\"\""
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
